/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"
#include "BTreeNode.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  PageFile pf;
  //B+ tree variables we need for indexing
  BTreeIndex index;
  IndexCursor cursor;
  BTLeafNode leaf;
  BTNonLeafNode nonleaf;
  RecordFile rf_index;
  bool have_index;
  bool no_indexing = true;
  bool has_key_cond = false;
  bool has_value_cond = false;
  //End of B+ tree variables
  
  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  have_index  = index.open(table+".idx",'r'); //This value will be checked if there's an existent indexed file
 
	if(have_index==0) //index file exists
	{
		no_indexing=false;
		pf.open(table+".idx",'r');
    
		rid.pid = rid.sid = 0;
		count = 0;
		int EQ_key,NE_key,LT_key,GT_key,LE_key,GE_key;
		EQ_key = NE_key =  LT_key =  GT_key =  LE_key =  GE_key = -1; 
		for (unsigned i = 0; i < cond.size(); i++) {	//check if any of the conditions is on "value" //ADDED!!!////////////////////////////
			if(cond[i].attr == 2)
				has_value_cond = true;
		}												/////////////////////////////////////////////////////////////////////////////////////
		for (unsigned i = 0; i < cond.size(); i++)
		{
			if(cond[i].attr==1&&cond[i].comp==SelCond::EQ) {
				EQ_key = atoi(cond[i].value);
			}
			else if(cond[i].attr==1&&cond[i].comp==SelCond::NE) {
				//need to use non index method;
				NE_key = atoi(cond[i].value);
				//no_indexing = true;
				//goto check_indexing;
			}

			else if(cond[i].attr==1&&cond[i].comp==SelCond::LT) {
				if (LT_key==-1) {
					LT_key = atoi(cond[i].value);
				}
				else if(atoi(cond[i].value )< LT_key &&  LT_key!=-1 ) {
					LT_key = atoi(cond[i].value);
				} 
			}

			else if(cond[i].attr==1&&cond[i].comp==SelCond::LE) {
				if (LE_key==-1) {
					LE_key = atoi(cond[i].value);
				}
				else if(atoi(cond[i].value )< LE_key &&  LE_key!=-1 ) {
					LE_key = atoi(cond[i].value);
				}
			}

			else if(cond[i].attr==1&&cond[i].comp==SelCond::GT) {
				if (GT_key==-1) {
					GT_key = atoi(cond[i].value);
				}

				else if(atoi(cond[i].value )> GT_key &&  GT_key!=-1 ) {
					GT_key = atoi(cond[i].value);
				}
               
			}

			else if(cond[i].attr==1&&cond[i].comp==SelCond::GE) {
				if (GE_key==-1) {
					GE_key = atoi(cond[i].value);
				}
				else if(atoi(cond[i].value )> GE_key &&  GE_key!=-1 ) {
					GE_key = atoi(cond[i].value);
				} 
			}
		}
		if (!(GT_key>-1|| GE_key>-1) && NE_key >-1)
		{
			no_indexing = true;
			goto check_indexing;
		}
       
		if(EQ_key>-1||LT_key>-1||GT_key>-1||LE_key>-1|| GE_key>-1) {
			has_key_cond = true;
		}
		else
		{
			if(attr != 1 || has_value_cond){			//CHANGED!!!/////////////////////////////////////////////////////////////////
				no_indexing = true;
				goto check_indexing;
			}
		}

		//get the key to start from left
		if(EQ_key>0) {
			key  = EQ_key;
		}
		else if(GT_key>-1||GE_key>-1) {
			key = (GT_key > GE_key) ? GT_key :GE_key; //get the bigger key to start

		}
		else  {
			key = 0;
		}

	{//to protect from goto
        int status  = index.locate(key,cursor);
        int fail_tolerence = 0;
		do{
			status = index.readForward(cursor,key,rid) ;
			if(!(attr == 1 && !has_value_cond))			//skip only if "key" and no "value"	//ADDED!!!/////////////////////////////////////////
			{
				rf.read(rid, key, value); //read from disk again
			}
			// check the conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) {
				// compute the difference between the tuple value and the condition value
				switch (cond[i].attr) {
				case 1:              
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					diff = strcmp(value.c_str(), cond[i].value);
					break;
				}
            
				// skip the tuple if any condition is not met
				if (cond[i].attr==1)
					switch (cond[i].comp) {//for key cond
					case SelCond::EQ:                  
						if (diff != 0) goto check_indexing;
						break;
					case SelCond::NE:
						if (diff == 0) goto next_tuple_index;
						break;
					case SelCond::GT:
						//else if (diff <=0) goto check_indexing;
						if (diff <=0) goto next_tuple_index;
						break;
					case SelCond::LT:
						if (diff >= 0) goto check_indexing;
						break;
					case SelCond::GE:
						if (diff < 0) goto next_tuple_index;
						break;
					case SelCond::LE:
						if (diff > 0) goto check_indexing;
						break;
					}
  
				if(cond[i].attr==2)
					switch (cond[i].comp) {//for value cond
					case SelCond::EQ:                  
						if (diff != 0) goto next_tuple_index;
						break;
					case SelCond::NE:
						if (diff == 0) goto next_tuple_index;
						break;
					case SelCond::GT:
						if (diff <=0) goto next_tuple_index;
						break;
					case SelCond::LT:
						if (diff >= 0) goto next_tuple_index;
						break;
					case SelCond::GE:
						if (diff < 0) goto next_tuple_index;
						break;
					case SelCond::LE:
						if (diff > 0) goto next_tuple_index;
						break;
					}
			}
   
			fail_tolerence = 0;
			// print the tuple 
			switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
			}
			count++;
			next_tuple_index:
			  ;
		}while ( status==0);
      
        // ++rid;
	}//to protect from goto

	check_indexing:

		index.close();
		pf.close();
		rf_index.close();
		// move to the next tuple
    
	}

  if(no_indexing)	//else
  {
	// scan the table file from the beginning
	rid.pid = rid.sid = 0;
	count = 0;
	while (rid < rf.endRid()) {
		if(attr == 4 && cond.size()==0)	//If only count(*) with no conditions, then just count until rid reaches endRid()
		{
			count++;
			++rid;
			continue;
		}	

		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) {
			fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
			goto exit_select;
		}

		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
			// compute the difference between the tuple value and the condition value
			switch (cond[i].attr) {
			case 1:
				diff = key - atoi(cond[i].value);
				break;
			case 2:
				diff = strcmp(value.c_str(), cond[i].value);
				break;
			}

			// skip the tuple if any condition is not met
			switch (cond[i].comp) {
			case SelCond::EQ:
				if (diff != 0) goto next_tuple;
				break;
			case SelCond::NE:
				if (diff == 0) goto next_tuple;
				break;
			case SelCond::GT:
				if (diff <= 0) goto next_tuple;
				break;
			case SelCond::LT:
				if (diff >= 0) goto next_tuple;
				break;
			case SelCond::GE:
				if (diff < 0) goto next_tuple;
				break;
			case SelCond::LE:
				if (diff > 0) goto next_tuple;
				break;
			}
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple:
		++rid;
		}
	}

	// print matching tuple count if "select count(*)"
	if (attr == 4) {
	fprintf(stdout, "%d\n", count);
	}
	rc = 0;

	// close the table file and return
	exit_select:
	rf.close();
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	int key; //Key extracted
	RecordFile currFile;
	currFile.open(table +".tbl",'w'); //File opened or created if non-existent
	string file=loadfile;
	string tuple; //Extracted tuple from del file
	string value; //Value extracted
	ifstream load_table(file.c_str());
	RecordId insertID;
	
	BTreeIndex ind; //b+ tree declaration for index option
	if(index) {     
		ind.open(table+".idx",'w');
	}
	
	if(load_table.is_open())
	{
		while(load_table.good())
		{
			if(getline(load_table,tuple))
			{
				parseLoadLine(tuple,key,value);
				if(value.length()>0) 
				{
					currFile.append (key,value,insertID);     
					if(index) 
					{
						ind.insert(key,insertID);
					}
				}
			}
			value="";
		}
		
		if(index)
		{
			ind.close(); //Close b+ tree index
		}
		load_table.close(); //close table
		currFile.close();  //close recordFile 
	}
	
	else
	{
		cout<<"Unable to open"<<endl; //In case file cannot be handled or opened	//RC_FILE_OPEN_FAILED ?
	}
		
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key valuein
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
